%% internal functions
%%%-------------------------------------------------------------------
%% @doc metronome_subscribe public API
%% @end
%%%-------------------------------------------------------------------

-module(metronome_subscribe).
-behaviour(gen_server).


%% --------------------------------------------------------------------
%% External exports
%% --------------------------------------------------------------------
-export([start_link/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-record(state, {subscribers, count}).
% subscriber = {pid, monitor}
%% ====================================================================
%% External functions
%% ====================================================================
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).


%% ====================================================================
init([]) ->
    {ok, #state{subscribers = [], count = 0}}.

%% --------------------------------------------------------------------
handle_call(cleanup, _From, State) ->
    [erlang:demonitor(Ref) || {_, Ref} <- State#state.subscribers],
    {reply, ok, State#state{subscribers = [], count = 0}};

handle_call(_Info, _From, State) ->
    {reply, ok, State}.

%% --------------------------------------------------------------------
handle_cast(beat_timer, State) ->
    Count = do_count(State#state.count),
    [Pid ! {metronome, Count} || {Pid, _} <- State#state.subscribers],
    {noreply, State#state{count = Count}};

handle_cast({join, Pid}, State) ->
    NewSubscribers = do_join(Pid, State#state.subscribers),
    {noreply, State#state{subscribers = NewSubscribers}};

handle_cast({quit, Pid}, State) ->
    NewSubscribers = do_quit(Pid, State#state.subscribers),
    {noreply, State#state{subscribers = NewSubscribers}};

handle_cast(_Info, State) ->
    {noreply, State}.

%% --------------------------------------------------------------------
handle_info({'DOWN', Ref, process, Pid, _Info}, State) ->
    NewSubscribers = do_quit(Pid, Ref, State#state.subscribers),
    {noreply, State#state{subscribers = NewSubscribers}};

handle_info(_Info, State) ->
    {noreply, State}.

%% --------------------------------------------------------------------
terminate(_Reason, _Status) ->
    ok.

%% --------------------------------------------------------------------
code_change(_oldvsn, Status, _extra) ->
    {ok, Status}.


%% ====================================================================
%% Local functions
%% ====================================================================
do_join(Pid, Subscribers) ->
    case lists:keymember(Pid, 1, Subscribers) of
        true -> Subscribers;
        false ->
            Ref = erlang:monitor(process, Pid),
            [{Pid, Ref} | Subscribers]
    end.

do_quit(Pid, Subscribers) ->
    case lists:keyfind(Pid, 1, Subscribers) of
        {Pid, Ref} ->
            erlang:demonitor(Ref),
            do_quit(Pid, Ref, Subscribers);
        _ ->
            Subscribers
    end.

do_quit(Pid, Ref, Subscribers) ->
    lists:delete({Pid, Ref}, Subscribers).

do_count(99999999) -> 1;
do_count(Count) -> Count + 1.
